#include "utils/Json.h"
#include "core/Logger.h"
#include "utils/make_unique.h"

#include <sstream>
#include <stdexcept>
#include <cctype>

namespace ems {
namespace utils {

enum class JsonType {
    Null,
    String,
    Number,
    Bool,
    Object,
    Array
};

struct Json::JsonData {
    JsonType type = JsonType::Null;
    std::string stringValue;
    double numberValue = 0.0;
    bool boolValue = false;
    std::map<std::string, Json> objectValue;
    std::vector<Json> arrayValue;
};

Json::Json() : data_(ems::utils::make_unique<JsonData>()) {}

Json::Json(const Json& other) : data_(ems::utils::make_unique<JsonData>(*other.data_)) {}

Json& Json::operator=(const Json& other) {
    if (this != &other) {
        data_ = ems::utils::make_unique<JsonData>(*other.data_);
    }
    return *this;
}

Json::~Json() = default;

bool Json::parse(const std::string& jsonString) {
    try {
        size_t pos = 0;
        skipWhitespace(jsonString, pos);
        parseValue(jsonString, pos);
        return true;
    } catch (const std::exception& e) {
        LOG_ERROR("JSON parse error: " << e.what());
        return false;
    }
}

bool Json::parse(std::istream& stream) {
    std::ostringstream oss;
    oss << stream.rdbuf();
    return parse(oss.str());
}

void Json::parseValue(const std::string& str, size_t& pos) {
    skipWhitespace(str, pos);
    
    if (pos >= str.length()) {
        throw std::runtime_error("Unexpected end of JSON");
    }
    
    char ch = str[pos];
    switch (ch) {
        case '{':
            parseObject(str, pos);
            break;
        case '[':
            parseArray(str, pos);
            break;
        case '"':
            parseString(str, pos);
            break;
        case 't':
        case 'f':
            parseBool(str, pos);
            break;
        case 'n':
            parseNull(str, pos);
            break;
        default:
            if (ch == '-' || std::isdigit(ch)) {
                parseNumber(str, pos);
            } else {
                throw std::runtime_error("Unexpected character: " + std::string(1, ch));
            }
            break;
    }
}

void Json::parseObject(const std::string& str, size_t& pos) {
    data_->type = JsonType::Object;
    pos++; // skip '{'
    
    skipWhitespace(str, pos);
    if (pos < str.length() && str[pos] == '}') {
        pos++;
        return;
    }
    
    while (pos < str.length()) {
        skipWhitespace(str, pos);
        
        if (str[pos] != '"') {
            throw std::runtime_error("Expected string key in object");
        }
        
        Json keyJson;
        keyJson.parseString(str, pos);
        std::string key = keyJson.asString();
        
        skipWhitespace(str, pos);
        if (pos >= str.length() || str[pos] != ':') {
            throw std::runtime_error("Expected ':' after object key");
        }
        pos++;
        
        Json value;
        value.parseValue(str, pos);
        data_->objectValue[key] = value;
        
        skipWhitespace(str, pos);
        if (pos >= str.length()) {
            throw std::runtime_error("Unexpected end of JSON object");
        }
        
        if (str[pos] == '}') {
            pos++;
            break;
        } else if (str[pos] == ',') {
            pos++;
        } else {
            throw std::runtime_error("Expected ',' or '}' in object");
        }
    }
}

void Json::parseArray(const std::string& str, size_t& pos) {
    data_->type = JsonType::Array;
    pos++; // skip '['
    
    skipWhitespace(str, pos);
    if (pos < str.length() && str[pos] == ']') {
        pos++;
        return;
    }
    
    while (pos < str.length()) {
        Json value;
        value.parseValue(str, pos);
        data_->arrayValue.push_back(value);
        
        skipWhitespace(str, pos);
        if (pos >= str.length()) {
            throw std::runtime_error("Unexpected end of JSON array");
        }
        
        if (str[pos] == ']') {
            pos++;
            break;
        } else if (str[pos] == ',') {
            pos++;
        } else {
            throw std::runtime_error("Expected ',' or ']' in array");
        }
    }
}

void Json::parseString(const std::string& str, size_t& pos) {
    data_->type = JsonType::String;
    pos++; // skip opening '"'
    
    std::string result;
    while (pos < str.length() && str[pos] != '"') {
        if (str[pos] == '\\') {
            pos++;
            if (pos >= str.length()) {
                throw std::runtime_error("Unexpected end of string escape");
            }
            
            switch (str[pos]) {
                case '"': result += '"'; break;
                case '\\': result += '\\'; break;
                case '/': result += '/'; break;
                case 'b': result += '\b'; break;
                case 'f': result += '\f'; break;
                case 'n': result += '\n'; break;
                case 'r': result += '\r'; break;
                case 't': result += '\t'; break;
                default:
                    result += str[pos];
                    break;
            }
        } else {
            result += str[pos];
        }
        pos++;
    }
    
    if (pos >= str.length()) {
        throw std::runtime_error("Unterminated string");
    }
    
    pos++; // skip closing '"'
    data_->stringValue = result;
}

void Json::parseNumber(const std::string& str, size_t& pos) {
    data_->type = JsonType::Number;
    
    size_t start = pos;
    if (str[pos] == '-') {
        pos++;
    }
    
    while (pos < str.length() && std::isdigit(str[pos])) {
        pos++;
    }
    
    if (pos < str.length() && str[pos] == '.') {
        pos++;
        while (pos < str.length() && std::isdigit(str[pos])) {
            pos++;
        }
    }
    
    if (pos < str.length() && (str[pos] == 'e' || str[pos] == 'E')) {
        pos++;
        if (pos < str.length() && (str[pos] == '+' || str[pos] == '-')) {
            pos++;
        }
        while (pos < str.length() && std::isdigit(str[pos])) {
            pos++;
        }
    }
    
    std::string numberStr = str.substr(start, pos - start);
    data_->numberValue = std::stod(numberStr);
}

void Json::parseBool(const std::string& str, size_t& pos) {
    data_->type = JsonType::Bool;
    
    if (str.substr(pos, 4) == "true") {
        data_->boolValue = true;
        pos += 4;
    } else if (str.substr(pos, 5) == "false") {
        data_->boolValue = false;
        pos += 5;
    } else {
        throw std::runtime_error("Invalid boolean value");
    }
}

void Json::parseNull(const std::string& str, size_t& pos) {
    if (str.substr(pos, 4) == "null") {
        data_->type = JsonType::Null;
        pos += 4;
    } else {
        throw std::runtime_error("Invalid null value");
    }
}

void Json::skipWhitespace(const std::string& str, size_t& pos) const {
    while (pos < str.length() && std::isspace(str[pos])) {
        pos++;
    }
}

std::string Json::asString() const {
    return data_->stringValue;
}

int Json::asInt() const {
    return static_cast<int>(data_->numberValue);
}

double Json::asDouble() const {
    return data_->numberValue;
}

bool Json::asBool() const {
    return data_->boolValue;
}

std::string Json::getString(const std::string& key, const std::string& defaultValue) const {
    if (data_->type == JsonType::Object) {
        auto it = data_->objectValue.find(key);
        if (it != data_->objectValue.end() && it->second.isString()) {
            return it->second.asString();
        }
    }
    return defaultValue;
}

int Json::getInt(const std::string& key, int defaultValue) const {
    if (data_->type == JsonType::Object) {
        auto it = data_->objectValue.find(key);
        if (it != data_->objectValue.end() && it->second.isNumber()) {
            return it->second.asInt();
        }
    }
    return defaultValue;
}

double Json::getDouble(const std::string& key, double defaultValue) const {
    if (data_->type == JsonType::Object) {
        auto it = data_->objectValue.find(key);
        if (it != data_->objectValue.end() && it->second.isNumber()) {
            return it->second.asDouble();
        }
    }
    return defaultValue;
}

bool Json::getBool(const std::string& key, bool defaultValue) const {
    if (data_->type == JsonType::Object) {
        auto it = data_->objectValue.find(key);
        if (it != data_->objectValue.end() && it->second.isBool()) {
            return it->second.asBool();
        }
    }
    return defaultValue;
}

Json Json::getObject(const std::string& key) const {
    if (data_->type == JsonType::Object) {
        auto it = data_->objectValue.find(key);
        if (it != data_->objectValue.end()) {
            return it->second;
        }
    }
    return Json();
}

std::vector<Json> Json::getArray(const std::string& key) const {
    if (data_->type == JsonType::Object) {
        auto it = data_->objectValue.find(key);
        if (it != data_->objectValue.end() && it->second.isArray()) {
            return it->second.data_->arrayValue;
        }
    }
    return {};
}

std::map<std::string, Json> Json::getMap() const {
    if (data_->type == JsonType::Object) {
        return data_->objectValue;
    }
    return {};
}

bool Json::isNull() const { return data_->type == JsonType::Null; }
bool Json::isString() const { return data_->type == JsonType::String; }
bool Json::isNumber() const { return data_->type == JsonType::Number; }
bool Json::isBool() const { return data_->type == JsonType::Bool; }
bool Json::isObject() const { return data_->type == JsonType::Object; }
bool Json::isArray() const { return data_->type == JsonType::Array; }

}  // namespace utils
}  // namespace ems